/*
 * jxSwing Project (2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxSwing.component.table;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Time;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.activation.UnsupportedDataTypeException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.jxSwing.component.table.cellEditor.BeanEditor;
import org.jxSwing.component.table.cellEditor.ComboEditor;
import org.jxSwing.component.table.cellEditor.DateFieldEditor;
import org.jxSwing.component.table.cellEditor.HourFieldEditor;
import org.jxSwing.component.table.cellEditor.NumericEditor;
import org.jxSwing.component.table.cellEditor.YesNoEditor;
import org.jxSwing.component.table.cellRenderer.BeanRenderer;
import org.jxSwing.component.table.cellRenderer.DateRenderer;
import org.jxSwing.component.table.cellRenderer.DefaultRenderer;
import org.jxSwing.component.table.cellRenderer.HourRenderer;
import org.jxSwing.component.table.cellRenderer.NumericRenderer;
import org.jxSwing.component.table.cellRenderer.PercentageRenderer;
import org.jxSwing.component.table.cellRenderer.URLRenderer;
import org.jxSwing.component.table.cellRenderer.YesNoRenderer;
import org.jxSwing.i18n.I18N;
import org.jxUtils.TypeUtil;
import org.jxUtils.check.CheckUtil;
import org.jxUtils.enumeration.CSVSeparator;
import org.jxUtils.exception.InvalidValueException;
import org.jxUtils.file.JXFile;
import org.jxUtils.image.ImageUtil;
import org.jxUtils.system.SystemProperties;

import sun.awt.image.ImageFormatException;

/**
 * @author Denilson Edinaldo Pinto
 * @porject jxSwing
 * @package org.jxSwing.component.table
 * @date 30/04/2014 - 09:04:44
 */
public final class BeanTableUtil {
	
	/**
	 * @param <Bean>
	 * @param bean
	 * @param table
	 * @param columnName
	 * @param columnValue
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws ParseException
	 */
	@SuppressWarnings({"unchecked", "rawtypes"})
	private static <Bean> void setStringValue(final Bean bean, final BeanTable<Bean> table, final String columnName, final String columnValue) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, ParseException {
	
		final BeanTableColumn column = table.getColumnByAlias(columnName);
		
		//
		if(column == null) {
			
			throw new InvalidValueException(I18N.component().invalidValueToColumnName(columnName));
		}
		
		final Class columnType = column.getType();
		final Method methodSet = column.getBeanMethodSet();
		
		final String value = columnValue != null ? columnValue.trim() : null;
		
		//
		if(CheckUtil.isNullOrEmpty(value)) {
			
			return;
			
		}else if(TypeUtil.isPrimitive(columnType)) {
			
			final TableCellRenderer renderer = table.getCellRenderer(0, table.getColumnIndex(column.getName()));
			
			//
			if(TypeUtil.isDateOrTime(columnType)) {
				
				methodSet.invoke(bean, ((DateRenderer)renderer).parse(value));
				
			}else if(TypeUtil.isNumber(columnType)) {
				
				final BigDecimal numberValue = new BigDecimal(value.replace(",", "."));
				
				//
				if(BigDecimal.class.isAssignableFrom(columnType)) {
					
					methodSet.invoke(bean, numberValue);
					
				}else if(Double.class.isAssignableFrom(columnType)) {
					
					methodSet.invoke(bean, numberValue.doubleValue());
					
				}else if(Float.class.isAssignableFrom(columnType)) {
					
					methodSet.invoke(bean, numberValue.floatValue());
					
				}else if(BigInteger.class.isAssignableFrom(columnType)) {
					
					methodSet.invoke(bean, new BigInteger(String.valueOf(numberValue.intValue())));
					
				}else if(Long.class.isAssignableFrom(columnType)) {
					
					methodSet.invoke(bean, numberValue.longValue());
					
				}else if(Short.class.isAssignableFrom(columnType)) {
					
					methodSet.invoke(bean, numberValue.shortValue());
					
				}else {
					
					throw new RuntimeException(new UnsupportedDataTypeException(I18N.component().notSupportedTypeByTable(columnType)));
				}
				
			}else if(Character.class.isAssignableFrom(columnType)) {
				
				methodSet.invoke(bean, value.charAt(0));
				
			}else if(Byte.class.isAssignableFrom(columnType)) {
				
				methodSet.invoke(bean, new Byte(value));
				
			}else if(Boolean.class.isAssignableFrom(columnType)) {
				
				methodSet.invoke(bean, new Boolean(value));
				
			}else {
				
				methodSet.invoke(bean, value);
			}
			
		}else if(columnType.isEnum()) {
			
			//
			if(Character.isDigit(value.charAt(0))) {
				
				try {
					methodSet.invoke(bean, Enum.valueOf(columnType, "_" + value));
					
				}catch(final Throwable throwable) {
					
					methodSet.invoke(bean, Enum.valueOf(columnType, "$" + value));
				}
				
			}else {
				
				methodSet.invoke(bean, Enum.valueOf(columnType, value));
			}
			
		}else if(TypeUtil.isImageIcon(columnType)) {
			
			throw new RuntimeException(new UnsupportedDataTypeException(I18N.component().notSupportedTypeByTable(columnType)));
			
		}else if(TypeUtil.isCollection(columnType)) {
			
			throw new RuntimeException(new UnsupportedDataTypeException(I18N.component().notSupportedTypeByTable(columnType)));
			
		}else if(TypeUtil.isMap(columnType)) {
			
			throw new RuntimeException(new UnsupportedDataTypeException(I18N.component().notSupportedTypeByTable(columnType)));
			
		}else {
			
			throw new RuntimeException(new UnsupportedDataTypeException(I18N.component().notSupportedTypeByTable(columnType)));
		}
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @param imageFile
	 * @param imageFormat
	 * @param tableName
	 * @throws IOException
	 */
	private static <Bean> void toImage(final BeanTable<Bean> table, final File imageFile, final String imageFormat) throws IOException {
	
		ImageIO.write(toImage(table), imageFormat, imageFile);
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 */
	static <Bean> void applyEditorAndRenderer(final BeanTable<Bean> table) {
	
		// Editor de Hora
		table.setDefaultRenderer(Time.class, new HourRenderer());
		table.setDefaultEditor(Time.class, new HourFieldEditor());
		
		// Editor de URL
		final URLRenderer urlRenderer = new URLRenderer();
		
		table.setDefaultRenderer(URL.class, urlRenderer);
		table.addMouseListener(urlRenderer);
		table.addMouseMotionListener(urlRenderer);
		
		table.setDefaultRenderer(Boolean.class, new YesNoRenderer());
		table.setDefaultEditor(Boolean.class, new YesNoEditor());
		
		//
		for(final BeanTableColumn beanTableColumn : table.getColumns()) {
			
			//
			SwingUtilities.invokeLater(new Runnable() {
				
				@Override
				public void run() {
				
					//
					final TableColumn tableColumn = table.getColumn(beanTableColumn.getAlias());
					
					// Se não é um Tipo "Primitivo"
					if( !TypeUtil.isPrimitive(beanTableColumn.getType())) {
						
						// Se o Tipo for um Enum
						if(beanTableColumn.getType().isEnum()) {
							
							// Editor de Enum (ComboBox)
							tableColumn.setCellRenderer(new DefaultRenderer(SwingConstants.CENTER));
							tableColumn.setCellEditor(new ComboEditor(beanTableColumn.getType().getEnumConstants()));
							
						}else {
							
							// Editor de Objetos Complexos
							tableColumn.setCellRenderer(new BeanRenderer(I18N.component().details()));
							tableColumn.setCellEditor(new BeanEditor(I18N.component().details()));
						}
						
					}else {
						
						// Se o Tipo for uma Data, Hora ou Calendário
						if(TypeUtil.isDateOrTime(beanTableColumn.getType())) {
							
							boolean isDateType = false;
							String pattern = null;
							String mask = null;
							
							//
							if(java.sql.Time.class.isAssignableFrom(beanTableColumn.getType())) {
								
								// 
								if(Locale.getDefault().getCountry().equals("BR")) {
									
									pattern = "HH:mm:ss";
									mask = "##:##:##";
									
								}else {
									
									pattern = "HH:mm:ss a";
									mask = "##:##:## UU";
								}
								
							}else if(java.sql.Timestamp.class.isAssignableFrom(beanTableColumn.getType())) {
								
								// 
								if(Locale.getDefault().getCountry().equals("BR")) {
									
									pattern = "HH:mm:ss.SSS";
									mask = "##:##:##.###";
									
								}else {
									
									pattern = "HH:mm:ss.SSS a";
									mask = "##:##:##.### UU";
								}
								
							}else if(java.sql.Date.class.isAssignableFrom(beanTableColumn.getType())) {
								
								// 
								if(Locale.getDefault().getCountry().equals("BR")) {
									
									pattern = "dd/MM/yyyy";
									mask = "##/##/####";
									
								}else {
									
									pattern = "yyyy-MM-dd";
									mask = "####-##-##";
								}
								
								isDateType = true;
								
							}else {
								
								// 
								if(Locale.getDefault().getCountry().equals("BR")) {
									
									pattern = "dd/MM/yyyy HH:mm:ss.SSS";
									mask = "##/##/#### ##:##:##.###";
									
								}else {
									
									pattern = "yyyy-MM-dd HH:mm:ss.SSS a";
									mask = "####-##-## ##:##:##.### UU";
								}
								
								isDateType = true;
							}
							
							mask = CheckUtil.isNullOrAbsoluteEmpty(beanTableColumn.getMask()) ? mask : beanTableColumn.getMask().trim();
							
							tableColumn.setCellRenderer(isDateType ? new DateRenderer(pattern) : new HourRenderer(pattern));
							tableColumn.setCellEditor(isDateType ? new DateFieldEditor(mask, pattern) : new HourFieldEditor(mask));
							
						}else if(TypeUtil.isNumber(beanTableColumn.getType())) {
							
							final NumericEditor editor = new NumericEditor();
							
							//
							if(TypeUtil.isInteger(beanTableColumn.getType()) && beanTableColumn.isIndicatesPercentage()) {
								
								tableColumn.setCellRenderer(new PercentageRenderer());
								
							}else {
								
								//
								final NumericRenderer renderer = new NumericRenderer();
								
								renderer.setMoney(beanTableColumn.isMoney());
								editor.setMoney(beanTableColumn.isMoney());
								
								// Se não for um campo monetário
								if( !beanTableColumn.isMoney()) {
									
									renderer.setDecimalDigits(beanTableColumn.getDecimalDigits());
									editor.setDecimalDigits(beanTableColumn.getDecimalDigits());
								}
								
								//
								tableColumn.setCellRenderer(renderer);
							}
							
							// Editor de Números
							tableColumn.setCellEditor(editor);
						}
					}
				}
			});
		}
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @param csvFile
	 * @param separator
	 * @throws IOException
	 */
	static <Bean> void fromCSV(final BeanTable<Bean> table, final File csvFile, final CSVSeparator separator) throws IOException {
	
		fromCSV(table, Files.readAllLines(Paths.get(csvFile.toURI()), Charset.defaultCharset()), separator);
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @param csvLines
	 * @param separator
	 * @throws IOException
	 */
	static <Bean> void fromCSV(final BeanTable<Bean> table, final List<String> csvLines, final CSVSeparator separator) throws IOException {
	
		final List<Bean> rows = new ArrayList<>();
		
		int colIndex = 0;
		
		List<String> columnNames = null;
		
		//
		for(final String line : csvLines) {
			
			colIndex = 0;
			
			final String[] columns = line.split(separator.toString());
			
			//
			if(columnNames == null) {
				
				columnNames = Arrays.asList(columns);
				
				continue;
			}
			
			try {
				@SuppressWarnings("unchecked")
				final Bean bean = (Bean)table.getBeanClass().newInstance();
				
				//
				for(final String columnValue : columns) {
					
					setStringValue(bean, table, columnNames.get(colIndex), columnValue.replace("\"", ""));
					
					colIndex++;
				}
				
				rows.add(bean);
				
			}catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException | ParseException exception) {
				
				throw new RuntimeException(exception.getMessage(), exception);
			}
		}
		
		//
		table.setRows(rows);
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @param xlsFile
	 * @throws IOException
	 */
	static <Bean> void fromXLS(final BeanTable<Bean> table, final File xlsFile) throws IOException {
	
		final FileInputStream file = new FileInputStream(xlsFile);
		
		// Get the workbook instance for XLS file
		final HSSFWorkbook workbook = new HSSFWorkbook(file);
		
		// Get first sheet from the workbook
		final HSSFSheet sheet = workbook.getSheetAt(0);
		
		// Get iterator to all the rows in current sheet
		final Iterator<Row> rowIterator = sheet.iterator();
		
		final List<Bean> rows = new ArrayList<>();
		
		final List<String> columnNames = new ArrayList<>();
		
		boolean isFirstRow = true;
		
		// Para cada Linha
		while(rowIterator.hasNext()) {
			
			final Row row = rowIterator.next();
			
			// Get iterator to all cells of current row
			final Iterator<Cell> cellIterator = row.cellIterator();
			
			//
			if(isFirstRow) {
				
				// Para cada Coluna
				while(cellIterator.hasNext()) {
					
					final Cell cell = cellIterator.next();
					
					columnNames.add(cell.getStringCellValue());
				}
				
				isFirstRow = false;
				
			}else {
				
				try {
					@SuppressWarnings("unchecked")
					final Bean bean = (Bean)table.getBeanClass().newInstance();
					
					int colIndex = 0;
					
					// Para cada Coluna
					while(cellIterator.hasNext()) {
						
						final Cell cell = cellIterator.next();
						
						setStringValue(bean, table, columnNames.get(colIndex), cell.getStringCellValue());
						
						colIndex++;
					}
					
					rows.add(bean);
					
				}catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException | ParseException exception) {
					
					throw new RuntimeException(exception.getMessage(), exception);
				}
			}
		}
		
		//
		table.setRows(rows);
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @param separator
	 * @param exportInvisibleColumns
	 * @return csv String
	 * @throws IOException
	 */
	static <Bean> String toCSV(final BeanTable<Bean> table, final CSVSeparator separator, final boolean exportInvisibleColumns) throws IOException {
	
		final String newLine = SystemProperties.getLineSeparator();
		
		final StringBuilder data = new StringBuilder();
		
		//
		for(final BeanTableColumn column : table.getColumns()) {
			
			// Se não exporta Colunas Invisíveis
			if( !exportInvisibleColumns && !column.isVisible()) {
				
				continue;
			}
			
			data.append("\"" + column + "\"");
			data.append(separator);
		}
		
		//
		for(int rowIndex = 0; rowIndex < table.getRowCount(); rowIndex++) {
			
			data.append(newLine);
			
			//
			for(int columnIndex = 0; columnIndex < table.getColumnCount(); columnIndex++) {
				
				// Se não exporta Colunas Invisíveis
				if( !exportInvisibleColumns && !table.getColumn(columnIndex).isVisible()) {
					
					continue;
				}
				
				final Object value = table.getValueAt(rowIndex, columnIndex);
				
				//
				if(value != null) {
					
					//
					if(TypeUtil.isPrimitive(value.getClass())) {
						
						final TableCellRenderer renderer = table.getCellRenderer(rowIndex, columnIndex);
						
						//
						if(TypeUtil.isDateOrTime(value.getClass())) {
							
							final DateRenderer dateRenderer = (DateRenderer)renderer;
							
							data.append("\"" + dateRenderer.format(value) + "\"");
							
						}else if(TypeUtil.isRealNumber(value.getClass())) {
							
							final NumericRenderer numericRenderer = (NumericRenderer)renderer;
							
							data.append("\"" + numericRenderer.formatValue(value) + "\"");
							
						}else {
							
							data.append("\"" + value + "\"");
						}
						
					}else if(TypeUtil.isImageIcon(value)) {
						
						final ImageIcon image = (ImageIcon)value;
						
						// TODO: Implementar Internacionalização
						final String imageName = image.getDescription() != null ? image.getDescription() : "Image in [" + rowIndex + "," + columnIndex + "]";
						
						data.append("\"" + imageName + "\"");
						
					}else if(TypeUtil.isCollection(value.getClass())) {
						
						// TODO: Melhorar Implementação para Objetos Complexos
						data.append("\"" + value.toString() + "\"");
						
					}else if(TypeUtil.isMap(value.getClass())) {
						
						final Map<?, ?> map = (Map<?, ?>)value;
						
						final Iterator<?> iterator = map.keySet().iterator();
						
						data.append("\"[");
						
						boolean isFirst = true;
						
						//
						while(iterator.hasNext()) {
							
							final Object key = iterator.next();
							
							// TODO: Melhorar Implementação para Objetos Complexos
							data.append(( !isFirst ? ", " : "") + "[" + key + "=" + map.get(key) + "]");
							
							isFirst = false;
						}
						
						data.append("]\"");
						
					}else {
						
						// TODO: Melhorar Implementação para Objetos Complexos
						data.append("\"" + value.toString() + "\"");
					}
					
				}else {
					
					data.append("\"\"");
				}
				
				data.append(separator);
			}
		}
		
		return data.toString();
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @param gifFile
	 * @throws IOException
	 */
	static <Bean> void toGIF(final BeanTable<Bean> table, final File gifFile) throws IOException {
	
		toImage(table, gifFile, "gif");
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @param htmlFile
	 * @param exportInvisibleColumns
	 * @throws IOException
	 * @throws ImageFormatException
	 */
	static <Bean> void toHTML(final BeanTable<Bean> table, final File htmlFile, final boolean exportInvisibleColumns) throws IOException, ImageFormatException {
	
		final String separator = SystemProperties.getFileSeparator();
		
		final StringBuilder htmlText = new StringBuilder();
		
		htmlText.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">");
		htmlText.append("\n<table border=1 cellspacing=0 cellpadding=5>");
		htmlText.append("\n<tr>\n");
		
		//
		for(final BeanTableColumn column : table.getColumns()) {
			
			// Se não exporta Colunas Invisíveis
			if( !exportInvisibleColumns && !column.isVisible()) {
				
				continue;
			}
			
			htmlText.append("<td><b>" + column + "</b></td>");
		}
		
		//
		for(int rowIndex = 0; rowIndex < table.getRowCount(); rowIndex++) {
			
			htmlText.append("\n<tr>");
			
			//
			for(int columnIndex = 0; columnIndex < table.getColumnCount(); columnIndex++) {
				
				// Se não exporta Colunas Invisíveis
				if( !exportInvisibleColumns && !table.getColumn(columnIndex).isVisible()) {
					
					continue;
				}
				
				final Object value = table.getValueAt(rowIndex, columnIndex);
				
				//
				if(value != null) {
					
					//
					if(TypeUtil.isPrimitive(value.getClass())) {
						
						final TableCellRenderer renderer = table.getCellRenderer(rowIndex, columnIndex);
						
						//
						if(TypeUtil.isDateOrTime(value.getClass())) {
							
							final DateRenderer dateRenderer = (DateRenderer)renderer;
							
							htmlText.append("\n<td>" + dateRenderer.format(value) + "</td>");
							
						}else if(TypeUtil.isRealNumber(value.getClass())) {
							
							final NumericRenderer numericRenderer = (NumericRenderer)renderer;
							
							htmlText.append("\n<td>" + numericRenderer.formatValue(value) + "</td>");
							
						}else {
							
							htmlText.append("\n<td>" + value.toString() + "</td>");
						}
						
					}else if(value instanceof Collection<?>) {
						
						final Collection<?> collection = (Collection<?>)value;
						
						final Iterator<?> iterator = collection.iterator();
						
						final StringBuilder valuesText = new StringBuilder();
						
						valuesText.append("<table border=1 cellspacing=0 cellpadding=5>");
						
						//
						while(iterator.hasNext()) {
							
							final Object valueAux = iterator.next();
							
							valuesText.append("\n<tr>");
							// TODO: Melhorar Implementação para Objetos Complexos
							valuesText.append("\n<td>" + (valueAux != null ? valueAux.toString() : "") + "</td>");
							valuesText.append("\n</tr>");
						}
						
						valuesText.append("\n</table>");
						
						htmlText.append("\n<td>" + valuesText.toString() + "</td>");
						
					}else if(value instanceof Map<?, ?>) {
						
						final Map<?, ?> map = (Map<?, ?>)value;
						
						final Iterator<?> iterator = map.keySet().iterator();
						
						final StringBuilder valuesText = new StringBuilder();
						
						valuesText.append("<table border=1 cellspacing=0 cellpadding=5>");
						
						//
						while(iterator.hasNext()) {
							
							final Object key = iterator.next();
							
							valuesText.append("\n<tr>");
							// TODO: Melhorar Implementação para Objetos Complexos
							valuesText.append("\n<td>" + key + "=" + map.get(key) + "</td>");
							valuesText.append("\n</tr>");
						}
						
						valuesText.append("\n</table>");
						
						htmlText.append("\n<td>" + valuesText.toString() + "</td>");
						
					}else if(TypeUtil.isImageIcon(value)) {
						
						final ImageIcon image = (ImageIcon)value;
						
						final File dirImame = new File(htmlFile.isDirectory() ? htmlFile : htmlFile.getParentFile(), "images/");
						
						if( !dirImame.exists()) {
							
							dirImame.mkdirs();
						}
						
						// TODO: Implementar Internacionalização
						final String imageName = image.getDescription() != null ? image.getDescription() : "Image in [" + rowIndex + "," + columnIndex + "]";
						
						final File imageFile = new File(dirImame, imageName);
						
						ImageUtil.saveImage(image, imageFile, "jpeg");
						
						htmlText.append("\n<td><img src=\"images" + separator + imageName + "\" name=\"" + imageName + "\" align=center whidth=562 height=422 border=0></td>");
						
					}else {
						
						// TODO: Melhorar Implementação para Objetos Complexos
						htmlText.append("\n<td>" + value.toString() + "</td>");
					}
					
				}else {
					
					htmlText.append("\n<td></td>");
				}
			}
			
			htmlText.append("\n</tr>");
		}
		
		htmlText.append("\n</table>");
		
		//
		new JXFile(htmlFile).writeString(htmlText.toString());
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @return bufferedImage
	 * @throws IOException
	 */
	static <Bean> BufferedImage toImage(final BeanTable<Bean> table) throws IOException {
	
		table.preparePrintTable();
		
		final int w = Math.max(table.getWidth(), table.getTableHeader().getWidth());
		final int h = table.getHeight() + table.getTableHeader().getHeight();
		
		final BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		
		final Graphics2D graphics2d = bufferedImage.createGraphics();
		
		table.getTableHeader().paint(graphics2d);
		
		graphics2d.translate(0, table.getTableHeader().getHeight());
		
		table.paint(graphics2d);
		
		graphics2d.dispose();
		
		table.restorePrintTable();
		
		return bufferedImage;
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @param jpgFile
	 * @throws IOException
	 */
	static <Bean> void toJPG(final BeanTable<Bean> table, final File jpgFile) throws IOException {
	
		toImage(table, jpgFile, "jpg");
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @param pngFile
	 * @throws IOException
	 */
	static <Bean> void toPNG(final BeanTable<Bean> table, final File pngFile) throws IOException {
	
		toImage(table, pngFile, "png");
	}
	
	/**
	 * @param <Bean>
	 * @param table
	 * @param xlsFile
	 * @param tableName
	 * @param exportInvisibleColumns
	 * @throws Throwable
	 */
	static <Bean> void toXLS(final BeanTable<Bean> table, final File xlsFile, final String tableName, final boolean exportInvisibleColumns) throws Throwable {
	
		final HSSFWorkbook workbook = new HSSFWorkbook();
		final HSSFSheet sheet = workbook.createSheet(tableName);
		
		//
		try (FileOutputStream stream = new FileOutputStream(xlsFile)) {
			
			int rowSheetIndex = 0;
			int headerIndex = 0;
			
			final HSSFRow rowHeader = sheet.createRow(rowSheetIndex++);
			
			//
			for(final BeanTableColumn column : table.getColumns()) {
				
				// Se não exporta Colunas Invisíveis
				if( !exportInvisibleColumns && !column.isVisible()) {
					
					continue;
				}
				
				rowHeader.createCell(headerIndex++).setCellValue(column.getAlias());
			}
			
			//
			for(int rowIndex = 0; rowIndex < table.getRowCount(); rowIndex++) {
				
				final HSSFRow row = sheet.createRow(rowSheetIndex++);
				
				//
				for(int columnIndex = 0; columnIndex < table.getColumnCount(); columnIndex++) {
					
					// Se não exporta Colunas Invisíveis
					if( !exportInvisibleColumns && !table.getColumn(columnIndex).isVisible()) {
						
						continue;
					}
					
					final Object value = table.getValueAt(rowIndex, columnIndex);
					
					//
					if(value != null) {
						
						//
						if(TypeUtil.isPrimitive(value.getClass())) {
							
							final TableCellRenderer renderer = table.getCellRenderer(rowIndex, columnIndex);
							
							//
							if(TypeUtil.isDateOrTime(value.getClass())) {
								
								final DateRenderer dateRenderer = (DateRenderer)renderer;
								
								row.createCell(columnIndex).setCellValue(dateRenderer.format(value));
								
							}else if(TypeUtil.isRealNumber(value.getClass())) {
								
								final NumericRenderer numericRenderer = (NumericRenderer)renderer;
								
								row.createCell(columnIndex).setCellValue(numericRenderer.formatValue(value));
								
							}else {
								
								row.createCell(columnIndex).setCellValue(value.toString());
							}
							
						}else if(TypeUtil.isImageIcon(value)) {
							
							final ImageIcon image = (ImageIcon)value;
							
							// TODO: Implementar Internacionalização
							final String imageName = image.getDescription() != null ? image.getDescription() : "Image in [" + rowIndex + "," + columnIndex + "]";
							
							row.createCell(columnIndex).setCellValue(imageName);
							
						}else if(TypeUtil.isCollection(value.getClass())) {
							
							// TODO: Melhorar Implementação para Objetos Complexos
							row.createCell(columnIndex).setCellValue(value.toString());
							
						}else if(TypeUtil.isMap(value.getClass())) {
							
							final Map<?, ?> map = (Map<?, ?>)value;
							
							final Iterator<?> iterator = map.keySet().iterator();
							
							final StringBuilder data = new StringBuilder();
							
							data.append("[");
							
							boolean isFirst = true;
							
							//
							while(iterator.hasNext()) {
								
								final Object key = iterator.next();
								
								// TODO: Melhorar Implementação para Objetos Complexos
								data.append(( !isFirst ? ", " : "") + "[" + key + "=" + map.get(key) + "]");
								
								isFirst = false;
							}
							
							data.append("]");
							
							row.createCell(columnIndex).setCellValue(data.toString());
							
						}else {
							
							// TODO: Melhorar Implementação para Objetos Complexos
							row.createCell(columnIndex).setCellValue(value.toString());
						}
						
					}else {
						
						row.createCell(columnIndex).setCellValue((String)null);
					}
				}
			}
			
			workbook.write(stream);
			
		}catch(final Throwable throwable) {
			
			throw throwable;
		}
	}
	
	/**
	 * @author Denilson Edinaldo Pinto
	 * @porject jxSwing
	 * @package org.jxSwing.component.table
	 * @date 09/06/2014 - 20:11:36
	 */
	public static enum ImageFormat {
		GIF,
		PNG,
		JPG
	}
}
